package com.ideal.frame.node.netty.client;

import cn.hutool.core.util.ObjectUtil;
import com.ideal.frame.node.entity.HeartbeatNodeTb;
import com.ideal.frame.node.entity.HeartbeatRootNodeTb;
import com.ideal.frame.node.entity.NodeTb;
import com.ideal.frame.node.entity.RootNodeTb;
import com.ideal.frame.node.enumeration.NodeLevel;
import com.ideal.frame.node.enumeration.NodeStatus;
import com.ideal.frame.node.netty.NettyClient;
import com.ideal.frame.node.service.HeartbeatRootNodeService;
import com.ideal.frame.node.service.HeartbeatService;
import com.ideal.frame.node.service.NodeService;
import com.ideal.frame.node.service.RootNodeService;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created by haoxy on 2018/10/17.
 * E-mail:hxyHelloWorld@163.com
 * github:https://github.com/haoxiaoyong1014
 *
 * EchoClientHandle继承了 ChannelInboundHandlerAdapter 的一个扩展(SimpleChannelInboundHandler),
 * 而ChannelInboundHandlerAdapter是ChannelInboundHandler的一个实现
 * ChannelInboundHandler提供了可以重写的各种事件处理程序方法
 *  目前，只需继承 SimpleChannelInboundHandler或ChannelInboundHandlerAdapter 而不是自己实现处理程序接口。
 *  我们在这里重写了channelRead0（）事件处理程序方法
 */
@Component
@Slf4j
@ChannelHandler.Sharable
@ConditionalOnBean(NettyClient.class)
public class NettyClientHandle extends SimpleChannelInboundHandler<ByteBuf> {

    @Autowired
    private NettyClient nettyClient;

    @Autowired
    private HeartbeatService heartbeatService;

    @Autowired
    private HeartbeatRootNodeService heartbeatRootNodeService;

    @Autowired
    private NodeService nodeService;

    @Autowired
    private RootNodeService rootNodeService;

    private void saveOnline(Long nodeId){
        log.info("心跳检测---节点[{}]:{}", nodeId, "更新状态为已上线");
        // 心跳日志入库
        HeartbeatNodeTb heartbeat = new HeartbeatNodeTb();
        heartbeat.setNodeid(nodeId + "");
        heartbeat.setInsertdate(new Date());
        heartbeat.setUpdatedate(new Date());
        heartbeat.setStatus(NodeStatus.Active.getValue());
        heartbeatService.save(heartbeat);
        // 更新nodeTb的status字段（反映节点的实时状态）
        NodeTb nodeTb = nodeService.getById(nodeId);
        if(nodeTb != null){
            nodeTb.setStatus(NodeStatus.Active.getBoolValue());
            nodeService.save(nodeTb);
        }
    }

    private void saveOffline(Long nodeId){
        log.info("心跳检测---节点[{}]:{}", nodeId, "更新状态为已下线");
        // 心跳日志入库
        HeartbeatNodeTb heartbeat = new HeartbeatNodeTb();
        heartbeat.setNodeid(nodeId + "");
        heartbeat.setInsertdate(new Date());
        heartbeat.setUpdatedate(new Date());
        heartbeat.setStatus(NodeStatus.InActive.getValue());
        heartbeatService.save(heartbeat);
        // 更新nodeTb的status字段（反映节点的实时状态）
        NodeTb nodeTb = nodeService.getById(nodeId);
        if(nodeTb != null){
            nodeTb.setStatus(NodeStatus.InActive.getBoolValue());
            nodeService.save(nodeTb);
        }
    }

    private void saveOnlineRoot(Long nodeId){
        log.info("心跳检测---节点[{}]:{}", nodeId, "更新状态为已上线");
        // 心跳日志入库
        HeartbeatRootNodeTb heartbeat = new HeartbeatRootNodeTb();
        heartbeat.setNodeid(nodeId + "");
        heartbeat.setInsertdate(new Date());
        heartbeat.setUpdatedate(new Date());
        heartbeat.setStatus(NodeStatus.Active.getValue());
        heartbeatRootNodeService.save(heartbeat);
        // 更新nodeTb的status字段（反映节点的实时状态）
        RootNodeTb nodeTb = rootNodeService.getById(nodeId);
        if(nodeTb != null){
            nodeTb.setStatus(NodeStatus.Active.getBoolValue());
            rootNodeService.save(nodeTb);
        }
    }

    private void saveOfflineRoot(Long nodeId){
        log.info("心跳检测---节点[{}]:{}", nodeId, "更新状态为已下线");
        // 心跳日志入库
        HeartbeatRootNodeTb heartbeat = new HeartbeatRootNodeTb();
        heartbeat.setNodeid(nodeId + "");
        heartbeat.setInsertdate(new Date());
        heartbeat.setUpdatedate(new Date());
        heartbeat.setStatus(NodeStatus.InActive.getValue());
        heartbeatRootNodeService.save(heartbeat);
        // 更新nodeTb的status字段（反映节点的实时状态）
        RootNodeTb nodeTb = rootNodeService.getById(nodeId);
        if(nodeTb != null){
            nodeTb.setStatus(NodeStatus.InActive.getBoolValue());
            rootNodeService.save(nodeTb);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        String nowTimeStr = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        // 移除map中对应的channel记录
        NettyClientConstant.ChannelAndNodeId channelAndNodeId = NettyClientConstant.channelMaps.remove(host + ":" + port);
        log.error("客户端与[{}:{}]断开连接,断开的时间为：{}", host, port, nowTimeStr);
        super.channelInactive(ctx);
        // 定时线程 断线重连
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(() -> {
            nettyClient.doConncet(host, port, channelAndNodeId.getNodeId(),channelAndNodeId.getNoteLevel());
        }, 10, TimeUnit.SECONDS);
        // 心跳日志入库
        Integer noteLevel = channelAndNodeId.getNoteLevel();
        if (ObjectUtil.equal(NodeLevel.CHILD_ROOT.getValue(), noteLevel)) {
            this.saveOfflineRoot(channelAndNodeId.getNodeId());
        } else {
            this.saveOffline(channelAndNodeId.getNodeId());
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.info("客户端与[{}:{}]连接成功!", host, port);
        super.channelActive(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state() == IdleState.WRITER_IDLE) {
                //向服务端发送消息
                ctx.writeAndFlush(NettyClientConstant.heartbeatMsg).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        }
        super.userEventTriggered(ctx, evt);
    }

    /**
     *  每当从服务端接收到新数据时，都会使用收到的消息调用此方法 channelRead0(),在此示例中，接收消息的类型是ByteBuf。
     * @param channelHandlerContext
     * @param byteBuf
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        //从服务端收到消息时被调用
        InetSocketAddress ipSocket = (InetSocketAddress) channelHandlerContext.channel().remoteAddress();
        int port = ipSocket.getPort();
        String host = ipSocket.getHostString();
        log.info("客户端收到[{}:{}]的消息={}", host, port, byteBuf.toString(CharsetUtil.UTF_8));
        // 更新连接记录
        long nodeid = NettyClientConstant.channelMaps.get(host+":"+port).getNodeId();
        Integer noteLevel = NettyClientConstant.channelMaps.get(host + ":" + port).getNoteLevel();
        if (ObjectUtil.equal(NodeLevel.CHILD_ROOT.getValue(), noteLevel)) {
            this.saveOnlineRoot(nodeid);
        } else {
            this.saveOnline(nodeid);
        }
    }
}
